package domain;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

public class PDFLibrary {

    private static PDFLibrary library;
    private SortedSet<PDFdoc> docs;
    private List<PDFdoc> selection;
    private File storeDB;
    private int groupSize;
    private int curGroup;

    /**
     * Note the private constructor. The class must implement the Singleton
     * Pattern.
     *
     * @param filename
     */
    private PDFLibrary(String filename) {
        storeDB = new File(filename);
        docs = new TreeSet<>();
        selection = new ArrayList<>();
        curGroup = 0;
    }

    /**
     * Given a filename, returns an instance of PDFLibrary. The filename is used
     * to save library's content.
     *
     * @param filename
     * @return
     */
    public static PDFLibrary makePDFLibrary(String filename) {
        if (library == null) {
            library = new PDFLibrary(filename);
        }
        return library;
    }

    /**
     * Saves the library to the disc.
     */
    public void saveLibrary() {

        try (BufferedWriter out = new BufferedWriter(new FileWriter(storeDB))) {
            for (PDFdoc doc : docs) {
                StringBuilder sb = new StringBuilder();
                sb.append(doc.getFilePath()).append("#");
                sb.append(doc.getTitle()).append("#");
                sb.append(doc.getCurrentPageNumber()).append("#");
                sb.append((doc.tagsToString(","))).append(" #");
                sb.append(doc.authorsToString(",")).append(" #\n");
                out.write(sb.toString());
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    /**
     * Loads a library from the disc.
     */
    public void loadLibrary() {
        try (BufferedReader in = new BufferedReader(new FileReader(storeDB))) {
            String thisLine;
            while ((thisLine = in.readLine()) != null) {
                String[] stringArray = thisLine.split("#");
                PDFdoc newDoc = PDFdoc.makePDFdoc(new File(stringArray[0]));
                addDoc(newDoc);
                newDoc.setTitle(stringArray[1]);
                newDoc.setCurPage(Integer.parseInt(stringArray[2]));
                newDoc.addTags(stringArray[3].trim(), ",");
                newDoc.setAuthors(stringArray[4].trim(), ",");
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        selectDocsWithOneOfTheseTags(new ArrayList<String>());
    }

    /**
     * Adds a document to the library.
     *
     * @param doc document to be add
     */
    public boolean addDoc(PDFdoc doc) {
        return docs.add(doc);
    }

    /**
     * Removes a document from the library.
     *
     * @param doc document to be removed
     */
    public void removeDoc(PDFdoc doc) {
        docs.remove(doc);
    }

    /**
     * Returns the number of documents present in the library.
     *
     * @return number of documents present in the library
     */
    public int getNumberOfDocuments() {
        return docs.size();
    }

    /**
     * Given a set of tags, select the documents that have at least one of these
     * tags. The result is a possibly large set of documents referred as the
     * "selection".
     *
     * @param tags set of tags used as filter
     */
    public void selectDocsWithOneOfTheseTags(ArrayList<String> tags) {
        selection = new ArrayList<>();
        if (tags.isEmpty()) {
            selection.addAll(docs);
        } else {
            for (PDFdoc d : docs) {
                for (String t : d.getTags()) {
                    if (tags.contains(t.toUpperCase())) {
                        selection.add(d);
                        break;
                    }
                }
            }
        }
    }

    /**
     * Given a set of tags, select the documents that have all the tags. The
     * result is a possibly large set of documents referred as the "selection".
     *
     * @param tags set of tags used as filter
     */
    public void selectDocsWithAllTheseTags(ArrayList<String> currentTags) {
        selection = new ArrayList<>();
        if (currentTags.isEmpty()) {
            selection.addAll(docs);
        } else {
            for (PDFdoc d : docs) {
                if (d.getTags().containsAll(currentTags)) {
                    selection.add(d);
                }
            }
        }
    }

    /**
     * The selection is divided into subsets of documents called "groups". This
     * method returns the first group of the selection.
     *
     * @return first group of the selection
     */
    public List<PDFdoc> getFirstGroup() {
        int fromIndice = 0;
        int toIndice = Math.min(groupSize, selection.size());
        curGroup = 0;
        return selection.subList(fromIndice, toIndice);

    }

    /**
     * Returns the next group of documents.
     *
     * @return next group of the selection
     */
    public List<PDFdoc> getNextGroup() {
        if (curGroup + 1 != nbGroupsInSelection()) {
            curGroup++;
        }
        int fromIndice = curGroup * groupSize;
        int toIndice = Math.min(fromIndice + groupSize, selection.size());
        return selection.subList(fromIndice, toIndice);
    }

    /**
     * Returns the previous group of documents.
     *
     * @return previous group of the selection
     */
    public List<PDFdoc> getPreviousGroup() {
        if (curGroup != 0) {
            curGroup--;
        }
        int fromIndice = curGroup * groupSize;
        int toIndice = Math.min(fromIndice + groupSize, selection.size());
        return selection.subList(fromIndice, toIndice);
    }

    /**
     * Changes the size (number of elements) that from a group of documents.
     *
     * @param size number of elements per group
     */
    public void setGroupSize(int size) {
        groupSize = size;
    }

    /**
     * Returns the number of the current group of documents.
     *
     * @return current group number
     */
    public int getCurrentGroupNumber() {
        return curGroup;
    }

    /**
     * Returns the number of groups in the selection
     *
     * @return number of groups in the selection
     */
    public int nbGroupsInSelection() {
        return selectionSize() == 0 ? 1 : (int) Math.ceil(selectionSize() / (double) groupSize);
    }

    /**
     * Returns the number of elements in the selection.
     *
     * @return number of elements in selection
     */
    public int selectionSize() {
        return selection.size();
    }

    public String toString() {
        StringBuilder str = new StringBuilder();
        for (PDFdoc d : selection) {
            str.append(d.toString());
            str.append("\n");
        }
        return str.toString();
    }
}
